#include "CLogDatabase.h"

#include "CppSQLite3.h"
#include <sstream>
#include <iostream>
#include <boost/filesystem.hpp>

CppSQLite3DB* CLogDatabase::getDatabaseConnection() const
{
	return m_dbConnection;
}

std::string CLogDatabase::getDatabasePath() const
{
	return m_dbPath;
}

int CLogDatabase::closeDatabase()
{
	if (m_dbConnection) {
		m_dbConnection->close();
		delete m_dbConnection;
		m_dbConnection = nullptr;
	}

	return 0; // Success
}

int CLogDatabase::initDatabase()
{
	try {
		// Close existing connection if open
		if (m_dbConnection) {
			return 0;
		}

		// Ensure the directory exists
		boost::filesystem::path pathObj(m_dbPath);
		auto dir = pathObj.parent_path();
		if (!dir.empty() && !boost::filesystem::exists(dir)) {
			boost::filesystem::create_directories(dir);
		}

		// Open new database connection
		m_dbConnection = new CppSQLite3DB();
		m_dbConnection->open(m_dbPath.c_str());

		return createNoniusTable(); // Success

	}
	catch (const CppSQLite3Exception& e) {
		// Handle SQLite exceptions
		if (m_dbConnection) {
			delete m_dbConnection;
			m_dbConnection = nullptr;
		}
		return -1; // Error: SQLite exception
	}
	catch (const std::exception& e) {
		// Handle other exceptions
		if (m_dbConnection) {
			delete m_dbConnection;
			m_dbConnection = nullptr;
		}
		return -2; // Error: general exception
	}
}

int CLogDatabase::createNoniusTable()
{
	try {
		// Check if we have a database connection
		if (!m_dbConnection) {
			return -1; // Error: no database connection
		}
		
		// Check if the nonius table already exists
		if (m_dbConnection->tableExists("nonius")) {
			return 0; // Table already exists, success
		}
		
		// Create the nonius table
		std::string createTableSql = R"(
			CREATE TABLE nonius (
				id       INTEGER PRIMARY KEY AUTOINCREMENT,
				stm      INTEGER,
				ltm      INTEGER,
				name     TEXT,
				table_id INTEGER
			)
		)";
		
		// Execute the CREATE TABLE statement
		m_dbConnection->execDML(createTableSql.c_str());
		
		std::cout << "Successfully created nonius table" << std::endl;
		return 0; // Success
		
	} catch (const CppSQLite3Exception& e) {
		// Handle SQLite exceptions
		//std::cout << "SQLite error in createNoniusTable: " << e.errorMessage() << std::endl;
		return -2; // Error: SQLite exception
	} catch (const std::exception& e) {
		// Handle other exceptions
		std::cout << "General error in createNoniusTable: " << e.what() << std::endl;
		return -3; // Error: general exception
	}
}

int CLogDatabase::updateNoniusCursor(const std::string& tableName, int tableId, uint64_t lastTime)
{
	try {
		// Check if we have a database connection
		if (!m_dbConnection) {
			return -1; // Error: no database connection
		}
		
		// Try to get existing cursor information
		uint64_t existingStartTime, existingLastTime;
		int existingTableId;
		int result = getNoniusCursor(tableName, existingStartTime, existingLastTime, existingTableId);
		
		if (result == 0) {
			// Cursor exists, update it
			return updateNoniusCursorInternal(tableName, lastTime);
		} else if (result == -4) {
			// Cursor doesn't exist, create new one
			// Use current timestamp as start time for new cursor
			uint64_t startTime = static_cast<uint64_t>(time(NULL) * 1000); // Current time in milliseconds
			return insertNoniusCursor(tableName, tableId, startTime, lastTime);
		} else {
			// Other error occurred
			return result;
		}
		
	} catch (const std::exception& e) {
		// Handle exceptions
		std::cout << "Exception in updateNoniusCursor: " << e.what() << std::endl;
		return -5; // Error: general exception
	}
}

int CLogDatabase::insertNoniusCursor(const std::string& tableName, int tableId, uint64_t startTime, uint64_t lastTime)
{
	try {
		// Check if we have a database connection
		if (!m_dbConnection) {
			return -1; // Error: no database connection
		}
		
		// Check if cursor already exists for this table
		std::stringstream checkSql;
		checkSql << "SELECT id FROM nonius WHERE name = '" << tableName << "'";
		
		CppSQLite3Query query = m_dbConnection->execQuery(checkSql.str().c_str());
		if (!query.eof()) {
			// Cursor already exists, update it instead
			query.finalize();
			return updateNoniusCursorInternal(tableName, lastTime);
		}
		query.finalize();
		
		// Insert new cursor
		std::stringstream insertSql;
		insertSql << "INSERT INTO nonius (stm, ltm, name, table_id) VALUES ("
				 << startTime << ", " << lastTime << ", '" << tableName << "', " << tableId << ")";
		
		m_dbConnection->execDML(insertSql.str().c_str());
		
		std::cout << "Inserted nonius cursor for table: " << tableName << std::endl;
		return 0; // Success
		
	} catch (const CppSQLite3Exception& e) {
		// Handle SQLite exceptions
		//std::cout << "SQLite error in insertNoniusCursor: " << e.errorMessage() << std::endl;
		return -2; // Error: SQLite exception
	} catch (const std::exception& e) {
		// Handle other exceptions
		std::cout << "General error in insertNoniusCursor: " << e.what() << std::endl;
		return -3; // Error: general exception
	}
}

int CLogDatabase::updateNoniusCursorInternal(const std::string& tableName, uint64_t lastTime)
{
	try {
		// Check if we have a database connection
		if (!m_dbConnection) {
			return -1; // Error: no database connection
		}
		
		// Update the last time for the specified table
		std::stringstream updateSql;
		updateSql << "UPDATE nonius SET ltm = " << lastTime << " WHERE name = '" << tableName << "'";
		
		m_dbConnection->execDML(updateSql.str().c_str());
		
		std::cout << "Updated nonius cursor for table: " << tableName << " to time: " << lastTime << std::endl;
		return 0; // Success
		
	} catch (const CppSQLite3Exception& e) {
		// Handle SQLite exceptions
		//std::cout << "SQLite error in updateNoniusCursorInternal: " << e.errorMessage() << std::endl;
		return -2; // Error: SQLite exception
	} catch (const std::exception& e) {
		// Handle other exceptions
		std::cout << "General error in updateNoniusCursorInternal: " << e.what() << std::endl;
		return -3; // Error: general exception
	}
}

int CLogDatabase::getNoniusCursor(const std::string& tableName, uint64_t& startTime, uint64_t& lastTime, int& tableId)
{
	try {
		// Check if we have a database connection
		if (!m_dbConnection) {
			return -1; // Error: no database connection
		}
		
		// Query the cursor for the specified table
		std::stringstream selectSql;
		selectSql << "SELECT stm, ltm, table_id FROM nonius WHERE name = '" << tableName << "'";
		
		CppSQLite3Query query = m_dbConnection->execQuery(selectSql.str().c_str());
		if (query.eof()) {
			// No cursor found
			query.finalize();
			return -4; // Error: cursor not found
		}
		
		// Extract cursor information
		startTime = query.getInt64Field("stm");
		lastTime = query.getInt64Field("ltm");
		tableId = query.getIntField("table_id");
		
		query.finalize();
		
		return 0; // Success
		
	} catch (const CppSQLite3Exception& e) {
		// Handle SQLite exceptions
		//std::cout << "SQLite error in getNoniusCursor: " << e.errorMessage() << std::endl;
		return -2; // Error: SQLite exception
	} catch (const std::exception& e) {
		// Handle other exceptions
		std::cout << "General error in getNoniusCursor: " << e.what() << std::endl;
		return -3; // Error: general exception
	}
}

int CLogDatabase::deleteNoniusCursor(const std::string& tableName)
{
	try {
		// Check if we have a database connection
		if (!m_dbConnection) {
			return -1; // Error: no database connection
		}
		
		// Delete the cursor for the specified table
		std::stringstream deleteSql;
		deleteSql << "DELETE FROM nonius WHERE name = '" << tableName << "'";
		
		m_dbConnection->execDML(deleteSql.str().c_str());
		
		std::cout << "Deleted nonius cursor for table: " << tableName << std::endl;
		return 0; // Success
		
	} catch (const CppSQLite3Exception& e) {
		// Handle SQLite exceptions
		//std::cout << "SQLite error in deleteNoniusCursor: " << e.errorMessage() << std::endl;
		return -2; // Error: SQLite exception
	} catch (const std::exception& e) {
		// Handle other exceptions
		std::cout << "General error in deleteNoniusCursor: " << e.what() << std::endl;
		return -3; // Error: general exception
	}
}

void CLogDatabase::lock()
{
	m_mutex.lock();
}

void CLogDatabase::unlock()
{
	m_mutex.unlock();
}

int CLogDatabase::setDatabaseFilePath(const std::string& dbPath)
{
	m_dbPath = dbPath;
	return 0;
}